﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: Range.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Runtime.Serialization;

namespace BOL
{
    /// <summary>
    /// Represents a non-comparable range class
    /// </summary>
    [DataContract]
    public class Range<T> : IRange<T>
            where T : struct, IComparable
    {
        #region Public properties

        [DataMember]
        public T From { get; set; }

        [DataMember]
        public bool IsFromClosed { get; set; }

        [DataMember]
        public T To { get; set; }

        [DataMember]
        public bool IsToClosed { get; set; }

        #endregion

        #region Constructor

        public Range(T from, bool isFromClosed, T to, bool isToClosed)
        {
            From = from;
            IsFromClosed = isFromClosed;
            To = to;
            IsToClosed = isToClosed;
        }

        public Range(T from, T to) : this(from, true, to, true) { }

        #endregion

        #region Operator implementation

        public static bool operator ==(Range<T> left, Range<T> right)
        {
            if (left == null || right == null)
                return false;

            return left.Equals(right);
        }

        public static bool operator !=(Range<T> left, Range<T> right)
        {
            return !(left == right);
        }

        public static bool operator >(Range<T> left, Range<T> right)
        {
            return left.CompareTo(right) > 0;
        }

        public static bool operator >(Range<T> left, T right)
        {
            return left.To.CompareTo(right) > 0;
        }

        public static bool operator >=(Range<T> left, Range<T> right)
        {
            return left.CompareTo(right) >= 0;
        }

        public static bool operator >=(Range<T> left, T right)
        {
            return left.To.CompareTo(right) >= 0;
        }

        public static bool operator <(Range<T> left, Range<T> right)
        {
            return left.CompareTo(right) < 0;
        }

        public static bool operator <(Range<T> left, T right)
        {
            return left.From.CompareTo(right) < 0;
        }

        public static bool operator <=(Range<T> left, Range<T> right)
        {
            return left.CompareTo(right) <= 0;
        }

        public static bool operator <=(Range<T> left, T right)
        {
            return (left.From.CompareTo(right) <= 0);
        }

        public static IRange<T> operator |(Range<T> left, Range<T> right)
        {
            return left.Union(right);
        }

        public static IRange<T> operator &(Range<T> left, Range<T> right)
        {
            return left.Intersect(right);
        }

        #endregion

        #region Public methods

        public bool Contains(T value)
        {
            if (value.CompareTo(From) > 0 && value.CompareTo(To) < 0)
                return true;

            return (IsFromClosed && value.Equals(From)) || (IsToClosed && value.Equals(To));
        }

        public bool Overlaps(IRange<T> other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            return From.CompareTo(other.To) * To.CompareTo(other.From) <= 0;
        }

        public IRange<T> Intersect(IRange<T> other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            var from = other.From;
            var to = other.To;

            if (!Overlaps(other))
                return null;

            from = From.CompareTo(from) > 0 ? From : from;
            to = To.CompareTo(to) < 0 ? To : to;

            return new Range<T>(from, to);
        }

        public IRange<T> Union(IRange<T> other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            var from = other.From;
            var to = other.To;

            if (!Overlaps(other))
                return null;

            from = From.CompareTo(From) == -1 ? From : from;
            to = To.CompareTo(To) == 1 ? To : to;

            return new Range<T>(from, to);
        }

        #endregion

        #region IComparable, IComparable<IRange<T>>, IEquatable<IRange<T>> implementation

        public int CompareTo(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is IRange<T>))
                throw new InvalidCastException("The 'other' argument is not a IRange<T> object.");

            return CompareTo(other as IRange<T>);
        }

        public int CompareTo(IRange<T> other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            return From.CompareTo(other.From) & To.CompareTo(other.To);
        }

        public bool Equals(IRange<T> other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            return From.Equals(other.From) && To.Equals(other.To) && IsFromClosed.Equals(other.IsFromClosed) && IsToClosed.Equals(other.IsToClosed);
        }

        #endregion

        #region Object overriden

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is IRange<T>))
                throw new InvalidCastException("The 'other' argument is not a IRange<T> object.");

            return Equals(other as IRange<T>);
        }

        public override int GetHashCode()
        {
            return From.GetHashCode() ^ To.GetHashCode() ^ IsFromClosed.GetHashCode() ^ IsToClosed.GetHashCode();
        }

        public override string ToString()
        {
            return String.Format("{0}{1}, {2}{3}", (IsFromClosed) ? '[' : '(', From, To, (IsToClosed) ? ']' : ')');
        }

        #endregion
    }
}
